﻿# CoffeeScript

window.logger = 
    enabled : true,
    log : (message) ->
        if console and console.log
            console.log(message)
        else
            alert(message)

class window.Shader
    constructor : (@context) ->        
        @shaderProgram = @context.createProgram()
        @shaders = []
    addShader : (source, type, typeString) ->        
        shader = @context.createShader(type)
        @context.shaderSource(shader, source);
        @context.compileShader(shader);
        if not @context.getShaderParameter(shader, @context.COMPILE_STATUS)
            logger.log("ERROR IN "+typeString+ " SHADER : " + @context.getShaderInfoLog(shader))
            false        
        @shaders.push(shader)
        shader
    attachShader: (shader) ->
        @context.attachShader(@shaderProgram, shader)
    link : (callback) ->
        @context.linkProgram(@shaderProgram)        
    use : () ->
        @context.useProgram(@shaderProgram) 
    getUniformLocation : (loc) ->
        @context.getUniformLocation(@shaderProgram,loc)
    getAttribLocation : (loc)->
        @context.getAttribLocation(@shaderProgram,loc)

class window.Texture
    constructor : (@context, @url, callback) ->        
        @image = new Image()
        self = this
        @image.onload = () ->              
            GL = self.context
            texture=GL.createTexture()
            GL.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, true)
            GL.bindTexture(GL.TEXTURE_2D, texture)
            GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, this)
            GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR)
            GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST_MIPMAP_LINEAR)
            GL.generateMipmap(GL.TEXTURE_2D)
            
            #This does not seem to make a difference... to figure out why is it here
            #GL.bindTexture(GL.TEXTURE_2D, null)
            self.texture = texture
            if typeof callback is "function"
                callback()            
        @image.src = @url
        
class window.Scene3D extends Observable
    constructor: (@canvasId) ->
        super        
        @canvas = document.getElementById(@canvasId)
        @context = @canvas.getContext("webgl", {antialias: true}) || @canvas.getContext("experimental-webgl", {antialias: true})
        @shapes = []
        animation.bind(this, @animationLoop)
        
        GL = @context
        GL.enable(GL.DEPTH_TEST)
        GL.depthFunc(GL.LEQUAL)
        GL.clearColor(0.0, 0.0, 0.0, 0.0)
        GL.clearDepth(1.0)
        @PROJMATRIX=MatrixUtils.get_projection(100, @canvas.width/@canvas.height, 1, 1000)
        @MOVEMATRIX=MatrixUtils.get_I4()
        @VIEWMATRIX=MatrixUtils.get_I4();        
        MatrixUtils.translateZ(@VIEWMATRIX, -6)
    
    # add a shape to the scene
    add : (shape) ->               
        @shapes.push(shape)
        shape.context = @context 
        shape.scene = this
        @shapes = @shapes.sort( 
            (a,b)->
                if a.zIndex<b.zIndex then 1
                else if a.zIndex>b.zIndex then -1 else 0
        )
      
    #remove a shape from the scene  
    remove : (shape) ->
        @shapes.find(
         (elem, index, arr) ->
            elem.context = null 
            elem.scene = null
            arr.splice(index,1)
        )
    
    # event from animation object
    # this is triggered all the time
    # we go through all shapes, and recompute their location
    # we redraw the scene
    animationLoop : (self, appTime) ->
        if self.lastTime 
            dt = (appTime - self.lastTime) / 1000
            self.shapes.each((elem,index,arr)->
                # get force
                force = elem.getComposedForce()
                # compute velocity
                elem.velocity.dx += force.ax * dt
                elem.velocity.dy += force.ay * dt
                elem.velocity.dz += force.az * dt
                
                # compute location
                newx = elem.location.x + elem.velocity.dx * dt
                eventArgs = 
                    shape : elem
                    x : newx
                    y : elem.location.y
                    z : elem.location.z                    
                    time : appTime
                cancel = not elem.notify("onmoving",eventArgs)
                if not cancel
                    elem.location.x = newx
                    
                newy = elem.location.y + elem.velocity.dy * dt
                eventArgs = 
                    shape : elem
                    x : elem.location.x
                    y : newy           
                    z : elem.location.z         
                    time : appTime
                cancel = not elem.notify("onmoving",eventArgs); 
                if not cancel
                    elem.location.y = newy
                
                newz = elem.location.z + elem.velocity.dz * dt
                eventArgs = 
                    shape : elem
                    x : elem.location.x
                    y : elem.location.y
                    z : newz
                    time : appTime
                cancel = not elem.notify("onmoving",eventArgs)
                if not cancel
                    elem.location.z = newz
                true
            );                    
        self.lastTime = appTime
        # trigger scene redraw
        self.invalidate()
    invalidate : () ->
        GL = @context
        GL.enable(GL.DEPTH_TEST)
        GL.depthFunc(GL.LEQUAL)
        GL.clearColor(0.0, 0.0, 0.0, 0.0)
        GL.clearDepth(1.0)
        @shapes.each((elem,index,arr)->                
            elem.draw()
        )
        
class window.Shape3D extends Observable
    constructor : (@scene) ->
        super
        @size = 100
        @rotation = 
            x : 0
            y: 0
            z: 0
        @location = 
            x : 0
            y : 0
            z : 0
        @velocity = 
            dx : 0
            dy : 0
            dz : 0
        @forces = []
        @PROJMATRIX=@scene.PROJMATRIX;
        @MOVEMATRIX=@scene.MOVEMATRIX;
        @VIEWMATRIX=@scene.VIEWMATRIX;
    setSize : (size) ->
        #@PROJMATRIX=MatrixUtils.get_projection(40, @scene.canvas.width/@scene.canvas.height, 1, size)
    addForce : (id, ax, ay, az) ->
        force = 
            id : id
            ax : ax
            ay : ay
            az : az
        @forces.push(force)
    # adds a force / acceleration to the shape 
    getForce : (id) ->
        force = null;
        @forces.find((elem,index,arr)->
            if elem.id is id
                force = elem
        );
        if force
           force;
        else @addForce(id,0,0,0) 
        
    setForceAx : (id,ax) ->
        force = null;
        @forces.find((elem,index,arr)->
            if elem.id is id
                elem.ax = ax;
        );                           
    setForceAy : (id,ay) ->
        force = null;
        @forces.find((elem,index,arr)->
            if elem.id is id                
                elem.ay = ay;
        );                           
    setForceAz : (id,az) ->
        force = null;
        @forces.find((elem,index,arr)->
            if elem.id is id                
                elem.az = az;
        );  
    # removes an acceleration from the shape        
    removeForce : (id) ->
        @forces.find((elem,index,arr)->
            if elem.id is id
                arr.splice(index,1)
        );
        true
        
    # composes all added accelerations to get the overall value
    # we simply add the x and y components
    getComposedForce : () ->
        force = 
            id : "composed"
            ax : 0
            ay : 0
            az : 0
        @forces.each((elem,index,arr)->
            if elem.ax
                force.ax += elem.ax;
            if elem.ay
                force.ay += elem.ay;
            if elem.az
                force.az += elem.az;
        )
        force
                        
class window.Cube extends window.Shape3D
    constructor : (scene)->     
        super(scene)   
        #create shaders
        @shader = new Shader(@scene.context)
        @shader_vertex_source="\n\
            attribute vec3 position;\n\
            uniform mat4 Pmatrix;\n\
            uniform mat4 Vmatrix;\n\
            uniform mat4 Mmatrix;\n\
            attribute vec2 uv;\n\
            varying vec2 vUV;\n\
            void main(void) { //pre-built function\n\
            gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);\n\
            vUV=uv;\n\
            }"
        @shader_fragment_source="\n\
            precision mediump float;\n\
            uniform sampler2D sampler;\n\
            varying vec2 vUV;\n\
            \n\
            \n\
            void main(void) {\n\
            gl_FragColor = texture2D(sampler, vUV);\n\
            }"
        @vertexShader = @shader.addShader(@shader_vertex_source, @scene.context.VERTEX_SHADER, "VERTEX")
        @fragmentShader = @shader.addShader(@shader_fragment_source, @scene.context.FRAGMENT_SHADER, "FRAGMENT")
        @shader.attachShader(@vertexShader)
        @shader.attachShader(@fragmentShader)
        @shader.link()
        
        #get attributes from shader
        @_Pmatrix = @shader.getUniformLocation("Pmatrix")
        @_Vmatrix = @shader.getUniformLocation("Vmatrix")
        @_Mmatrix = @shader.getUniformLocation("Mmatrix")
        @_sampler = @shader.getUniformLocation("sampler")

        @_uv =  @shader.getAttribLocation("uv")
        @_position =  @shader.getAttribLocation("position")
        
        @scene.context.enableVertexAttribArray(@_uv);
        @scene.context.enableVertexAttribArray(@_position);
        
        #initialize cube vertex arrays
        @setSize(100);
        
        #initialize cube faces arrays
        @cube_faces = [
            0,1,2,
            0,2,3,

            4,5,6,
            4,6,7,

            8,9,10,
            8,10,11,

            12,13,14,
            12,14,15,

            16,17,18,
            16,18,19,

            20,21,22,
            20,22,23
        ]        
        @cube_faces_buffer = @scene.context.createBuffer()
        @scene.context.bindBuffer(@scene.context.ELEMENT_ARRAY_BUFFER, @cube_faces_buffer)
        @scene.context.bufferData(@scene.context.ELEMENT_ARRAY_BUFFER, new Uint16Array(@cube_faces), @scene.context.STATIC_DRAW)
    setSize : (size) ->
        actualSize = size / 100;
        @cube_vertices = [-actualSize,-actualSize,-actualSize,    0,0,
            actualSize,-actualSize,-actualSize,     1,0,
            actualSize, actualSize,-actualSize,     1,1,
            -actualSize, actualSize,-actualSize,    0,1,

            -actualSize,-actualSize, actualSize,    0,0,
            actualSize,-actualSize, actualSize,     1,0,
            actualSize, actualSize, actualSize,     1,1,
            -actualSize, actualSize, actualSize,    0,1,

            -actualSize,-actualSize,-actualSize,    0,0,
            -actualSize, actualSize,-actualSize,    1,0,
            -actualSize, actualSize, actualSize,    1,1,
            -actualSize,-actualSize, actualSize,    0,1,

            actualSize,-actualSize,-actualSize,     0,0,
            actualSize, actualSize,-actualSize,     1,0,
            actualSize, actualSize, actualSize,     1,1,
            actualSize,-actualSize, actualSize,     0,1,

            -actualSize,-actualSize,-actualSize,    0,0,
            -actualSize,-actualSize, actualSize,    1,0,
            actualSize,-actualSize, actualSize,     1,1,
            actualSize,-actualSize,-actualSize,     0,1,

            -actualSize, actualSize,-actualSize,    0,0,
            -actualSize, actualSize, actualSize,    1,0,
            actualSize, actualSize, actualSize,     1,1,
            actualSize,actualSize,-actualSize,     0,1]            
        @cube_vertex_buffer = @scene.context.createBuffer()
        @scene.context.bindBuffer(@scene.context.ARRAY_BUFFER, @cube_vertex_buffer)
        @scene.context.bufferData(@scene.context.ARRAY_BUFFER, new Float32Array(@cube_vertices), @scene.context.STATIC_DRAW)
        
    setTexture : (url,callback)->
        @texture = new Texture(@scene.context,url, callback);      
    draw : () ->
        #use a particular shader - we use this for textured cubes
        @shader.use()
        
        #not sure what this is - it seems it does not do much as it works witout it too?
        #@scene.context.uniform1i(@_sampler, 0)
        GL = @scene.context
        
        MatrixUtils.set_I4(@MOVEMATRIX);
        
        #set rotation - to the correct values
        MatrixUtils.rotateZ(@MOVEMATRIX, MatrixUtils.degToRad(@rotation.z));
        MatrixUtils.rotateY(@MOVEMATRIX, MatrixUtils.degToRad(@rotation.y));
        MatrixUtils.rotateX(@MOVEMATRIX, MatrixUtils.degToRad(@rotation.x));
        
        #set location - to the correct values
        MatrixUtils.translateZ(@MOVEMATRIX, MatrixUtils.degToRad(@location.z));
        MatrixUtils.translateY(@MOVEMATRIX, MatrixUtils.degToRad(@location.y));
        MatrixUtils.translateX(@MOVEMATRIX, MatrixUtils.degToRad(@location.x));                
        
        # I think these are previously called
        # however we should not clear the scene on drawing on a shape
        #GL.viewport(0.0, 0.0, @scene.canvas.width, @scene.canvas.height);
        #GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);
        GL.uniformMatrix4fv(@_Pmatrix, false, @PROJMATRIX);
        GL.uniformMatrix4fv(@_Vmatrix, false, @VIEWMATRIX);
        GL.uniformMatrix4fv(@_Mmatrix, false, @MOVEMATRIX);
        
        if @texture          
          GL.activeTexture(GL.TEXTURE0)
          GL.bindTexture(GL.TEXTURE_2D, @texture.texture)
                  
        GL.bindBuffer(GL.ARRAY_BUFFER, @cube_vertex_buffer);
        GL.vertexAttribPointer(@_location, 3, GL.FLOAT, false,4*(3+2),0) ;
        GL.vertexAttribPointer(@_uv, 2, GL.FLOAT, false,4*(3+2),3*4) ;
        GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER,@cube_faces_buffer);
        GL.drawElements(GL.TRIANGLES, 6*2*3, GL.UNSIGNED_SHORT, 0);

        GL.flush();
        
    

    